diff --git a/src/components/DevboxCreatePage.tsx b/src/components/DevboxCreatePage.tsx index b830de2e..9c7b06c0 100644 --- a/src/components/DevboxCreatePage.tsx +++ b/src/components/DevboxCreatePage.tsx @@ -205,39 +205,87 @@ export const DevboxCreatePage = ({ currentField === "resource_size", ); - useInput((input, key) => { - // Handle result screen - if (result) { - if (input === "q" || key.escape || key.return) { - if (onCreate) { - onCreate(result); + // Main form input handler - active when not in metadata section + useInput( + (input, key) => { + // Handle result screen + if (result) { + if (input === "q" || key.escape || key.return) { + if (onCreate) { + onCreate(result); + } + onBack(); } - onBack(); + return; + } + + // Handle error screen + if (error) { + if (input === "r" || key.return) { + // Retry - clear error and return to form + setError(null); + } else if (input === "q" || key.escape) { + // Quit - go back to list + onBack(); + } + return; } - return; - } - // Handle error screen - if (error) { - if (input === "r" || key.return) { - // Retry - clear error and return to form - setError(null); - } else if (input === "q" || key.escape) { - // Quit - go back to list + // Handle creating state + if (creating) { + return; + } + + // Back to list + if (input === "q" || key.escape) { onBack(); + return; } - return; - } - // Handle creating state - if (creating) { - return; - } + // Submit form with Ctrl+S + if (input === "s" && key.ctrl) { + handleCreate(); + return; + } - // Handle metadata section FIRST (before general escape handler) - if (inMetadataSection) { + // Enter key on metadata field to enter metadata section + if (currentField === "metadata" && key.return) { + setInMetadataSection(true); + setSelectedMetadataIndex(0); + return; + } + + // Handle Enter on any field to submit + if (key.return) { + handleCreate(); + return; + } + + // Handle select field navigation using shared hooks + if (handleArchitectureNav(input, key)) return; + if (handleResourceSizeNav(input, key)) return; + + // Navigation (up/down arrows and tab/shift+tab) + if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) { + setCurrentField(fields[currentFieldIndex - 1].key); + return; + } + + if ( + (key.downArrow || (key.tab && !key.shift)) && + currentFieldIndex < fields.length - 1 + ) { + setCurrentField(fields[currentFieldIndex + 1].key); + return; + } + }, + { isActive: !inMetadataSection }, + ); + + // Metadata section input handler - active when in metadata section + useInput( + (input, key) => { const metadataKeys = Object.keys(formData.metadata); - // Selection model: 0 = "Add new", 1..n = Existing items, n+1 = "Done" const maxIndex = metadataKeys.length + 1; // Handle input mode (typing key or value) @@ -258,35 +306,31 @@ export const DevboxCreatePage = ({ setMetadataKey(""); setMetadataValue(""); setMetadataInputMode(null); - setSelectedMetadataIndex(0); // Back to "add new" row + setSelectedMetadataIndex(0); return; } else if (key.escape) { - // Cancel input setMetadataKey(""); setMetadataValue(""); setMetadataInputMode(null); return; } else if (key.tab) { - // Tab between key and value setMetadataInputMode(metadataInputMode === "key" ? "value" : "key"); return; } - return; // Don't process other keys while in input mode + return; } - // Navigation mode + // Navigation mode in metadata section if (key.upArrow && selectedMetadataIndex > 0) { setSelectedMetadataIndex(selectedMetadataIndex - 1); } else if (key.downArrow && selectedMetadataIndex < maxIndex) { setSelectedMetadataIndex(selectedMetadataIndex + 1); } else if (key.return) { if (selectedMetadataIndex === 0) { - // Add new setMetadataKey(""); setMetadataValue(""); setMetadataInputMode("key"); } else if (selectedMetadataIndex === maxIndex) { - // Done - exit metadata section setInMetadataSection(false); setSelectedMetadataIndex(0); setMetadataKey(""); @@ -296,16 +340,12 @@ export const DevboxCreatePage = ({ selectedMetadataIndex >= 1 && selectedMetadataIndex <= metadataKeys.length ) { - // Edit existing (selectedMetadataIndex - 1 gives array index) const keyToEdit = metadataKeys[selectedMetadataIndex - 1]; setMetadataKey(keyToEdit || ""); setMetadataValue(formData.metadata[keyToEdit] || ""); - - // Remove old entry const newMetadata = { ...formData.metadata }; delete newMetadata[keyToEdit]; setFormData({ ...formData, metadata: newMetadata }); - setMetadataInputMode("key"); } } else if ( @@ -313,70 +353,24 @@ export const DevboxCreatePage = ({ selectedMetadataIndex >= 1 && selectedMetadataIndex <= metadataKeys.length ) { - // Delete selected item (selectedMetadataIndex - 1 gives array index) const keyToDelete = metadataKeys[selectedMetadataIndex - 1]; const newMetadata = { ...formData.metadata }; delete newMetadata[keyToDelete]; setFormData({ ...formData, metadata: newMetadata }); - // Stay at same position or move to add new if we deleted the last item const newLength = Object.keys(newMetadata).length; if (selectedMetadataIndex > newLength) { setSelectedMetadataIndex(Math.max(0, newLength)); } } else if (key.escape || input === "q") { - // Exit metadata section setInMetadataSection(false); setSelectedMetadataIndex(0); setMetadataKey(""); setMetadataValue(""); setMetadataInputMode(null); } - return; - } - - // Back to list (only when not in metadata section) - if (input === "q" || key.escape) { - onBack(); - return; - } - - // Submit form - if (input === "s" && key.ctrl) { - handleCreate(); - return; - } - - // Handle Enter on create field - if (currentField === "create" && key.return) { - handleCreate(); - return; - } - - // Handle select field navigation using shared hooks - if (handleArchitectureNav(input, key)) return; - if (handleResourceSizeNav(input, key)) return; - - // Navigation (up/down arrows and tab/shift+tab) - if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) { - setCurrentField(fields[currentFieldIndex - 1].key); - return; - } - - if ( - (key.downArrow || (key.tab && !key.shift)) && - currentFieldIndex < fields.length - 1 - ) { - setCurrentField(fields[currentFieldIndex + 1].key); - return; - } - - // Enter key on metadata field to enter metadata section - if (currentField === "metadata" && key.return) { - setInMetadataSection(true); - setSelectedMetadataIndex(0); // Start at "add new" row - return; - } - }); + }, + { isActive: inMetadataSection }, + ); // Validate custom resource configuration const validateCustomResources = (): string | null => { @@ -589,6 +583,7 @@ export const DevboxCreatePage = ({ onChange={(value) => setFormData({ ...formData, [field.key]: value }) } + onSubmit={handleCreate} isActive={isActive} placeholder={field.placeholder} /> diff --git a/src/components/NetworkPolicyCreatePage.tsx b/src/components/NetworkPolicyCreatePage.tsx index 080bcf38..2f9bf8e7 100644 --- a/src/components/NetworkPolicyCreatePage.tsx +++ b/src/components/NetworkPolicyCreatePage.tsx @@ -136,82 +136,80 @@ export const NetworkPolicyCreatePage = ({ currentField === "allow_devbox_to_devbox", ); - useInput((input, key) => { - // Handle result screen - if (result) { - if (input === "q" || key.escape || key.return) { - if (onCreate) { - onCreate(result); + useInput( + (input, key) => { + // Handle result screen + if (result) { + if (input === "q" || key.escape || key.return) { + if (onCreate) { + onCreate(result); + } + onBack(); } - onBack(); + return; } - return; - } - // Handle error screen - if (error) { - if (input === "r" || key.return) { - // Retry - clear error and return to form - setError(null); - } else if (input === "q" || key.escape) { - // Quit - go back to list - onBack(); + // Handle error screen + if (error) { + if (input === "r" || key.return) { + // Retry - clear error and return to form + setError(null); + } else if (input === "q" || key.escape) { + // Quit - go back to list + onBack(); + } + return; } - return; - } - - // Handle submitting state - if (submitting) { - return; - } - // Handle hostnames expanded mode - let FormListManager handle input - if (hostnamesExpanded) { - return; - } + // Handle submitting state + if (submitting) { + return; + } - // Back to list - if (input === "q" || key.escape) { - onBack(); - return; - } + // Back to list + if (input === "q" || key.escape) { + onBack(); + return; + } - // Submit form with Ctrl+S - if (input === "s" && key.ctrl) { - handleSubmit(); - return; - } + // Submit form with Ctrl+S + if (input === "s" && key.ctrl) { + handleSubmit(); + return; + } - // Handle Enter on submit field - if (currentField === "submit" && key.return) { - handleSubmit(); - return; - } + // Handle Enter on hostnames field to expand + if (currentField === "allowed_hostnames" && key.return) { + setHostnamesExpanded(true); + return; + } - // Handle Enter on hostnames field to expand - if (currentField === "allowed_hostnames" && key.return) { - setHostnamesExpanded(true); - return; - } + // Handle Enter on any field to submit (including text/select fields) + if (key.return) { + handleSubmit(); + return; + } - // Handle select field navigation - if (handleAllowAllNav(input, key)) return; - if (handleDevboxNav(input, key)) return; + // Handle select field navigation + if (handleAllowAllNav(input, key)) return; + if (handleDevboxNav(input, key)) return; - // Navigation between fields (up/down arrows and tab/shift+tab) - if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) { - setCurrentField(fields[currentFieldIndex - 1].key); - return; - } + // Navigation between fields (up/down arrows and tab/shift+tab) + if ((key.upArrow || (key.tab && key.shift)) && currentFieldIndex > 0) { + setCurrentField(fields[currentFieldIndex - 1].key); + return; + } - if ( - (key.downArrow || (key.tab && !key.shift)) && - currentFieldIndex < fields.length - 1 - ) { - setCurrentField(fields[currentFieldIndex + 1].key); - return; - } - }); + if ( + (key.downArrow || (key.tab && !key.shift)) && + currentFieldIndex < fields.length - 1 + ) { + setCurrentField(fields[currentFieldIndex + 1].key); + return; + } + }, + { isActive: !hostnamesExpanded }, + ); const handleSubmit = async () => { // Validate required fields @@ -426,6 +424,7 @@ export const NetworkPolicyCreatePage = ({ setValidationError(null); } }} + onSubmit={handleSubmit} isActive={isActive} placeholder={ field.key === "name" diff --git a/src/components/form/FormTextInput.tsx b/src/components/form/FormTextInput.tsx index be2d933f..52a5d644 100644 --- a/src/components/form/FormTextInput.tsx +++ b/src/components/form/FormTextInput.tsx @@ -14,6 +14,8 @@ export interface FormTextInputProps { isActive: boolean; placeholder?: string; error?: string; + /** Called when Enter is pressed in the text input */ + onSubmit?: () => void; } export const FormTextInput = ({ @@ -23,6 +25,7 @@ export const FormTextInput = ({ isActive, placeholder, error, + onSubmit, }: FormTextInputProps) => { return ( @@ -31,6 +34,7 @@ export const FormTextInput = ({ value={value} onChange={onChange} placeholder={placeholder} + onSubmit={onSubmit} /> ) : (