import React, { useMemo } from 'react';
import { Button, XStack, YStack } from 'tamagui';
import { SidePanelShell } from './SidePanelShell.jsx';
import { FormField } from './FormField.jsx';
import { getIcon } from './IconMapper.jsx';
function defaultExpressionEvaluator(template, form) {
return template.replace(/\{\{(\w+)\}\}/g, (_match, fieldName) => form[fieldName] || '');
}
function renderAction(action, index, fallbackHandler) {
const IconComponent = action?.icon ? getIcon(action.icon) : null;
return {
id: action?.id || action?.label || `action-${index}`,
label: action?.label,
icon: action?.icon,
disabled: action?.disabled,
theme: action?.theme,
chromeless: action?.chromeless,
onPress: action?.onPress || fallbackHandler,
iconComponent: IconComponent
};
}
export function FormView({
open = false,
onClose = null,
title = 'Edit Record',
toolbar = [],
fields = [],
values = {},
onChange = () => {},
onSubmit = () => {},
onReset = () => {},
buttons = [],
loading = false,
errors = {},
children = null,
hideButtons = false,
width = 460
}) {
const processedFields = useMemo(() => {
return fields.map((field) => {
if (!field.expression) {
return field;
}
const expressionFunction = typeof field.expression === 'string'
? (form) => defaultExpressionEvaluator(field.expression, form)
: field.expression;
return {
...field,
expressionFunction,
readOnly: true
};
});
}, [fields]);
const computedValues = useMemo(() => {
return processedFields.reduce((accumulator, field) => {
if (field.expressionFunction) {
accumulator[field.id] = field.expressionFunction(values);
}
return accumulator;
}, {});
}, [processedFields, values]);
const mergedValues = {
...values,
...computedValues
};
const footerActions = useMemo(() => {
if (hideButtons) {
return [];
}
const sourceButtons = buttons.length > 0
? buttons
: [
{ label: 'Reset', chromeless: true, onPress: onReset },
{ label: 'Save', theme: 'active', onPress: onSubmit }
];
return sourceButtons.map((button, index) => renderAction(button, index, index === 0 ? onReset : onSubmit));
}, [buttons, hideButtons, onReset, onSubmit]);
const renderField = (field, index) => (
);
const renderChildren = () => {
if (!children) {
return null;
}
return React.Children.map(children, (child) => {
if (React.isValidElement(child) && child.type === FormField) {
const fieldId = child.props.id;
return React.cloneElement(child, {
value: mergedValues[fieldId],
onChange,
error: errors[fieldId],
disabled: child.props.disabled || loading
});
}
return child;
});
};
return (
{processedFields.map(renderField)}
{children ? (
{renderChildren()}
) : null}
{loading ? (
) : null}
);
}
export default FormView;