@letanure/resend-cli
Version:
A command-line interface for Resend email API
209 lines • 10.1 kB
JavaScript
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