UNPKG

@letanure/resend-cli

Version:

A command-line interface for Resend email API

209 lines 10.1 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Alert } from '@inkjs/ui'; import { Box, useInput } from 'ink'; import { useCallback, useState } from 'react'; import { InputWithSelector } from './InputWithSelector.js'; import { SelectField } from './SelectField.js'; import { TextInput } from './TextInput.js'; export const SimpleForm = ({ fields, onSubmit, onCancel, validateWith, initialData, onFormDataChange, }) => { const [formData, setFormData] = useState(() => { const initial = {}; for (const field of fields) { if (field.type === 'select' && field.options) { // Set default value to first option's value initial[field.name] = field.options[0]?.value || false; } else { initial[field.name] = ''; } } // Override with initialData if provided if (initialData) { Object.assign(initial, initialData); } return initial; }); const [currentField, setCurrentField] = useState(0); const [_currentSelectOption, setCurrentSelectOption] = useState(0); const [errors, setErrors] = useState({}); const [formError, setFormError] = useState(''); useInput((input, key) => { const currentFieldData = fields[currentField]; const isSelectField = currentFieldData?.type === 'select'; const isInputWithSelector = currentFieldData?.type === 'input-with-selector'; const isStackedSelect = isSelectField && currentFieldData?.display === 'stacked'; const isInlineSelect = isSelectField && currentFieldData?.display !== 'stacked'; const keyHandlers = [ { condition: () => key.escape, action: onCancel }, // Left arrow to exit form - only if NOT on an inline select field or input-with-selector { condition: () => key.leftArrow && !isInlineSelect && !isInputWithSelector, action: onCancel }, { condition: () => key.shift && key.tab, action: () => setCurrentField(currentField === 0 ? fields.length - 1 : currentField - 1), }, { condition: () => key.tab && !key.shift, action: () => setCurrentField(currentField === fields.length - 1 ? 0 : currentField + 1), }, // Special handling for stacked select fields { condition: () => key.downArrow && isStackedSelect, action: () => handleStackedSelectNavigation('down'), }, { condition: () => key.upArrow && isStackedSelect, action: () => handleStackedSelectNavigation('up'), }, // Default field navigation for non-stacked selects (circular) { condition: () => key.downArrow && !isStackedSelect, action: () => setCurrentField(currentField === fields.length - 1 ? 0 : currentField + 1), }, { condition: () => key.upArrow && !isStackedSelect, action: () => setCurrentField(currentField === 0 ? fields.length - 1 : currentField - 1), }, // Left/Right arrow for inline select fields only { condition: () => (key.leftArrow || key.rightArrow) && isInlineSelect, action: () => currentFieldData && handleSelectToggle(currentFieldData), }, { condition: () => input === ' ' && isSelectField, action: () => currentFieldData && handleSelectToggle(currentFieldData), }, { condition: () => key.return, action: handleFormSubmission }, ]; for (const handler of keyHandlers) { if (handler.condition()) { handler.action(); break; } } }); const handleFormSubmission = () => { const validationErrors = {}; let validatedData = null; let currentFormError = ''; if (validateWith) { const cleanedData = {}; for (const [key, value] of Object.entries(formData)) { if (typeof value === 'string') { cleanedData[key] = value.trim() === '' ? undefined : value.trim(); } else { cleanedData[key] = value; } } const result = validateWith.safeParse(cleanedData); if (result.success) { validatedData = result.data; } else { result.error.issues.forEach((issue) => { const field = issue.path[0]; if (typeof field === 'string') { validationErrors[field] = issue.message; } else { // Form-level error (no specific field) currentFormError = issue.message; } }); } } if (Object.keys(validationErrors).length === 0 && !currentFormError) { setFormError(''); if (validatedData) { // Pass validated, transformed data from schema onSubmit(validatedData); } else { // Fallback to cleaned raw data if no schema const cleanData = {}; for (const [key, value] of Object.entries(formData)) { if (typeof value === 'string') { cleanData[key] = value.trim(); } else { cleanData[key] = value; } } onSubmit(cleanData); } } else { setErrors(validationErrors); setFormError(currentFormError); } }; const handleFieldChange = useCallback((fieldName, value) => { const newFormData = { ...formData, [fieldName]: value }; setFormData(newFormData); // Notify parent of form data changes onFormDataChange?.(newFormData); // Clear error when user starts typing if (errors[fieldName]) { setErrors((prev) => ({ ...prev, [fieldName]: '' })); } // Clear form-level error when user starts typing if (formError) { setFormError(''); } }, [formData, onFormDataChange, errors, formError]); const handleSelectToggle = (field) => { if (field.options && field.options.length > 1) { const currentValue = formData[field.name]; const currentIndex = field.options.findIndex((option) => option.value === currentValue); const nextIndex = (currentIndex + 1) % field.options.length; const nextValue = field.options[nextIndex]?.value; handleFieldChange(field.name, nextValue); setCurrentSelectOption(nextIndex); } }; const handleStackedSelectNavigation = (direction) => { const currentFieldData = fields[currentField]; if (!currentFieldData?.options) { return; } const currentValue = formData[currentFieldData.name]; const currentIndex = currentFieldData.options.findIndex((option) => option.value === currentValue); const optionsLength = currentFieldData.options.length; if (direction === 'down') { if (currentIndex < optionsLength - 1) { // Move to next option const nextIndex = currentIndex + 1; const nextValue = currentFieldData.options[nextIndex]?.value; handleFieldChange(currentFieldData.name, nextValue); setCurrentSelectOption(nextIndex); } else { // Move to next field when on last option (non-circular for stacked selects) setCurrentField(Math.min(currentField + 1, fields.length - 1)); } } else if (direction === 'up') { if (currentIndex > 0) { // Move to previous option const prevIndex = currentIndex - 1; const prevValue = currentFieldData.options[prevIndex]?.value; handleFieldChange(currentFieldData.name, prevValue); setCurrentSelectOption(prevIndex); } else { // Move to previous field when on first option (non-circular for stacked selects) setCurrentField(Math.max(currentField - 1, 0)); } } }; return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [fields.map((field, index) => { if (field.type === 'select' && field.options) { return (_jsx(SelectField, { label: field.label, options: field.options, value: formData[field.name], isActive: currentField === index, helpText: field.helpText, errorMessage: errors[field.name], display: field.display, onToggle: () => handleSelectToggle(field) }, field.name)); } if (field.type === 'input-with-selector') { return (_jsx(InputWithSelector, { label: field.label, value: String(formData[field.name] || ''), onChange: (value) => handleFieldChange(field.name, value), placeholder: field.placeholder, helpText: field.helpText, isFocused: currentField === index, error: errors[field.name], onSelectorOpen: field.onSelectorOpen }, field.name)); } return (_jsx(TextInput, { label: field.label, value: String(formData[field.name] || ''), onChange: (value) => handleFieldChange(field.name, value), placeholder: field.placeholder, helpText: field.helpText, isFocused: currentField === index, error: errors[field.name] }, field.name)); }), formError && (_jsx(Box, { marginTop: 1, children: _jsx(Alert, { variant: "error", children: formError }) }))] })); }; //# sourceMappingURL=SimpleForm.js.map