From 4d94642ed6db8250915ecef898f0e3a0ea6d6aca Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 27 Jan 2025 20:07:08 -0500 Subject: [PATCH 01/33] feat: Upgraded package versions for NodeJS types, react, ink and ink ui libraries. Modified state to support duplicates and added Add Item + Remove Item buttons --- package.json | 18 +- src/Button.tsx | 22 + src/Form.tsx | 96 +- src/FormHeader.tsx | 2 +- src/ScrollArea.tsx | 75 ++ src/types.ts | 6 +- yarn.lock | 2300 +++++++++++++++++--------------------------- 7 files changed, 1080 insertions(+), 1439 deletions(-) create mode 100644 src/Button.tsx create mode 100644 src/ScrollArea.tsx diff --git a/package.json b/package.json index 23ad419..f69cc40 100644 --- a/package.json +++ b/package.json @@ -23,22 +23,22 @@ "license": "MIT", "bugs": "https://github.com/lukasbach/ink-form/issues", "devDependencies": { - "@types/node": "^14.14.45", + "@types/node": "20.14.8", "@types/react": "^18.2.41", - "ink": "^4.4.1", + "ink": "^5.1.0", "prettier": "^2.2.1", "publish-fast": "^0.0.20", "react": "^18.2.0", - "ts-node": "^10.9.1", + "tsx": "^4.19.2", "typedoc": "^0.25.4", "typescript": "^5.3.2" }, "scripts": { "start": "ts-node-esm src/demo/overview.tsx", - "demo:overview": "ts-node-esm src/demo/overview.tsx", - "demo:packagejson": "ts-node-esm src/demo/packagejson.tsx", - "demo:custommanager": "ts-node-esm src/demo/custommanager.tsx", - "demo:imperative": "ts-node-esm src/demo/imperative.ts", + "demo:overview": "tsx src/demo/overview.tsx", + "demo:packagejson": "tsx src/demo/packagejson.tsx", + "demo:custommanager": "tsx src/demo/custommanager.tsx", + "demo:imperative": "tsx src/demo/imperative.ts", "build": "tsc", "build:docs": "typedoc --out docs src/index.ts", "lint": "prettier --check .", @@ -47,11 +47,11 @@ "release": "publish-fast" }, "peerDependencies": { - "ink": ">=4", + "ink": ">=5", "react": ">=18" }, "dependencies": { - "ink-select-input": "^5.0.0", + "ink-select-input": "^6.0.0", "ink-text-input": "^6.0.0" }, "publishConfig": { diff --git a/src/Button.tsx b/src/Button.tsx new file mode 100644 index 0000000..3d2badb --- /dev/null +++ b/src/Button.tsx @@ -0,0 +1,22 @@ +import { Box, Text, useFocus, useInput } from 'ink'; +import React from 'react'; + +export function Button(props: { + label: string; + onClicked?: () => void; +}) { + const { isFocused } = useFocus({}); + useInput((input, key) => { + if (key.return && isFocused) { + props.onClicked?.(); + } + }); + + return + + + {props.label} + + + +} diff --git a/src/Form.tsx b/src/Form.tsx index 2fff508..d09aa65 100644 --- a/src/Form.tsx +++ b/src/Form.tsx @@ -7,11 +7,14 @@ import { FormFieldRenderer } from './FormFieldRenderer.js'; import { DescriptionRenderer } from './DescriptionRenderer.js'; import { canSubmit } from './canSubmit.js'; import { SubmitButton } from './SubmitButton.js'; +import { Button } from './Button.js'; export const Form: React.FC = props => { const isControlled = props.value !== undefined; const [currentTab, setCurrentTab] = useState(0); - const [value, setValue] = useState(props.value ?? {}); + const [isSubmitted, setIsSubmitted] = useState(false); + const [sections, setSections] = useState(props.form.sections); + const [value, setValue] = useState>>(props.value ?? Array.from({ length: sections.length }, () => ({}))); const [editingField, setEditingField] = useState(); const canSubmitForm = useMemo(() => canSubmit(props.form, value), [value, props.form]); const focusManager = useFocusManager(); @@ -29,21 +32,25 @@ export const Form: React.FC = props => { useEffect(() => { // Set initial values if (!isControlled) { - setValueAndPropagate({ - ...value, - ...props.form.sections - .map(section => - section.fields - .map(field => (field.initialValue !== undefined ? { [field.name]: field.initialValue } : {})) - .reduce((obj1, obj2) => ({ ...obj1, ...obj2 }), {}) - ) - .reduce((obj1, obj2) => ({ ...obj1, ...obj2 }), {}), - }); + setValue(Array.from({ length: sections.length }, () => ({}))); + + + // setValueAndPropagate({ + // ...value, + // ...props.form.sections + // .map(section => + // section.fields + // .map(field => (field.initialValue !== undefined ? { [field.name]: field.initialValue } : {})) + // .reduce((obj1, obj2) => ({ ...obj1, ...obj2 }), {}) + // ) + // .reduce((obj1, obj2) => ({ ...obj1, ...obj2 }), {}), + // }); } }, []); - const setValueAndPropagate = (value: object) => { - setValue(value); + const setValueAndPropagate = (index: number, newValue: Record) => { + value[index] = newValue + setValue(structuredClone(value)); props.onChange?.(value); }; @@ -58,33 +65,62 @@ export const Form: React.FC = props => { { isActive: !editingField } ); + const duplicateCurrentItem = () => { + const newTabs = [...sections] + newTabs.splice(currentTab + 1, 0, sections[currentTab]) + + const newValue = { ...value[currentTab]}; + value.splice(currentTab + 1, 0, newValue); + + setSections(newTabs); + setValue([...value]); + } + + const removeCurrentItem = () => { + const newTabs = [...sections] + newTabs.splice(currentTab, 1); + value.splice(currentTab, 1) + + setSections(newTabs); + setValue([...value]); + } + return ( - - - {!editingField && props.form.sections[currentTab].description && ( + !isSubmitted && + + {!editingField && sections[currentTab].description && ( - + )} {currentTab > props.form.sections.length - 1 ? null - : props.form.sections[currentTab].fields.map(field => ( - setValueAndPropagate({ ...value, [field.name]: v })} - onSetEditingField={setEditingField} - editingField={editingField} - customManagers={props.customManagers} - /> - ))} + : sections[currentTab].fields.map((field, index) => ( + setValueAndPropagate(currentTab, { ...value[currentTab], [field.name]: v })} + onSetEditingField={setEditingField} + editingField={editingField} + customManagers={props.customManagers} + /> + ))} + +