From 728b7eb28c75bb4709f4e0132d5dbfac281b4b8d Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 2 Dec 2025 22:23:05 -0700 Subject: [PATCH] (FEAT): Worked on the instruction list! This is great! Looking so much better than it did before! And it works the way I wanted it to! Yayy!!! The reorder stuff is awesome too! --- web/package-lock.json | 115 ++++++++++++++++++ web/package.json | 2 + .../components/forms/InstructionElement.tsx | 80 ++++++++++++ web/src/components/forms/InstructionForm.tsx | 45 +++++++ web/src/components/icons/DragIconSmall.tsx | 7 ++ web/src/pages/Create.tsx | 72 ++++++----- 6 files changed, 289 insertions(+), 32 deletions(-) create mode 100644 web/src/components/forms/InstructionElement.tsx create mode 100644 web/src/components/forms/InstructionForm.tsx create mode 100644 web/src/components/icons/DragIconSmall.tsx diff --git a/web/package-lock.json b/web/package-lock.json index b688f04..6df06f1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,10 +8,12 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", "@tailwindcss/vite": "^4.1.16", "axios": "^1.13.2", "eslint-plugin-react-dom": "^2.2.4", "eslint-plugin-react-x": "^2.2.4", + "motion": "^12.23.25", "react": "^19.1.1", "react-cookie": "^8.0.1", "react-dom": "^19.1.1", @@ -33,6 +35,45 @@ "vite": "^7.1.7" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -2676,6 +2717,33 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "12.23.25", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.25.tgz", + "integrity": "sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3332,6 +3400,47 @@ "node": "*" } }, + "node_modules/motion": { + "version": "12.23.25", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.25.tgz", + "integrity": "sha512-Fk5Y1kcgxYiTYOUjmwfXQAP7tP+iGqw/on1UID9WEL/6KpzxPr9jY2169OsjgZvXJdpraKXy0orkjaCVIl5fgQ==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.23.25", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3903,6 +4012,12 @@ "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", "license": "MIT" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/web/package.json b/web/package.json index 437610c..8ff4695 100644 --- a/web/package.json +++ b/web/package.json @@ -10,10 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", "@tailwindcss/vite": "^4.1.16", "axios": "^1.13.2", "eslint-plugin-react-dom": "^2.2.4", "eslint-plugin-react-x": "^2.2.4", + "motion": "^12.23.25", "react": "^19.1.1", "react-cookie": "^8.0.1", "react-dom": "^19.1.1", diff --git a/web/src/components/forms/InstructionElement.tsx b/web/src/components/forms/InstructionElement.tsx new file mode 100644 index 0000000..7c2d93b --- /dev/null +++ b/web/src/components/forms/InstructionElement.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState, type ChangeEvent } from "react"; +import type { Instruction } from "../../pages/Create"; +import { Reorder, useDragControls } from "motion/react"; +import DragIconSmall from "../icons/DragIconSmall"; + +interface InstructionElementProps { + instruction: Instruction; + index: number; + allowDelete: boolean; + onChange: (id: string, value: string) => void; + onDelete: (id: string) => void; +} + +export default function InstructionElement({ instruction, index, allowDelete, onChange, onDelete }: InstructionElementProps) { + const controls = useDragControls(); + + const [valid, setValid] = useState(true); + const [dirty, setDirty] = useState(false); + + // HANDLERS + const changeHandler = (e: ChangeEvent) => { + // No need to set many times + if (!dirty) setDirty(true); + + onChange(instruction.id, e.target.value); + } + + // EFFECTS + useEffect(() => { + if (dirty) + setValid(instruction.content !== ""); + }, [dirty, instruction]); + + return ( + +
+

{index + 1}.

+
+ -

- Please enter at least one step. -

-
+ + +